問題 §
以下のプログラムでXYZ0,XYZ1,XYZ2,XYZ3の順番を期待したのに、実際はXYZ0,XYZ2,XYZ3,XYZ1である。
using System;
using System.Threading.Tasks;
namespace CancelToken1
{
class Program
{
static async Task Main(string[] args)
{
try
{
var source = new System.Threading.CancellationTokenSource();
_ = Task.Delay(1000).ContinueWith((task) =>
{
Console.WriteLine("XYZ0");
source.Cancel();
Console.WriteLine("XYZ1");
});
await Task.Delay(-1, source.Token);
}
catch (System.Threading.Tasks.TaskCanceledException)
{
Console.WriteLine("XYZ2");
}
Console.WriteLine("XYZ3");
}
}
}
原因 §
source.Cancel();は非同期メソッドではないにも関わらず非同期的に実行される。つまり、Cancelメソッドは即座に待機中のメソッド(Delay)で例外(TaskCanceledException)を発生させるため、待機中のDelayメソッドの実行が即座に再開されてしまう。コンテキストスイッチのタイミングがまわってくるまで、Cancelメソッドの次のメソッド(Console.WriteLine("XYZ1");)は実行されない。
解決 §
待機中のメソッドが再開する前に実行すべきコードはCancelメソッドの呼び出しよりも手前に必ず置く。
たとえば以下のように書き換える。
Console.WriteLine("XYZ0");
Console.WriteLine("XYZ1"); // 移動された行
source.Cancel();